package felix.things.router.modbus.net;

import com.ghgande.j2mod.modbus.Modbus;
import com.ghgande.j2mod.modbus.io.AbstractModbusTransport;
import com.ghgande.j2mod.modbus.io.ModbusRTUTransport;
import com.ghgande.j2mod.modbus.io.ModbusTCPTransport;
import com.ghgande.j2mod.modbus.io.ModbusUDPTransport;
import com.ghgande.j2mod.modbus.net.AbstractSerialConnection;
import com.ghgande.j2mod.modbus.net.AbstractUDPTerminal;
import com.ghgande.j2mod.modbus.net.SerialConnection;
import com.ghgande.j2mod.modbus.util.SerialParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.*;

public class TestModbusMasterFactory {

    private static final Logger logger = LoggerFactory.getLogger(TestModbusMasterFactory.class);

    public static AbstractModbusTransport createModbusMaster(String address, AbstractSerialConnection serialConnection) {
        return createCustomModbusMaster(address, serialConnection);
    }

    public static AbstractModbusTransport createModbusMaster(String address) {
        return createCustomModbusMaster(address, null);
    }

    private static AbstractModbusTransport createCustomModbusMaster(String address, AbstractSerialConnection serialConnection) {
        String parts[] = address.split(" *: *");
        if (parts.length < 2) {
            throw new IllegalArgumentException("missing connect+ion information");
        }

        if (parts[0].equalsIgnoreCase("device")) {
            /*
             * Create a ModbusSerialListener with the default Modbus values of
             * 19200 baud, no parity, using the specified device. If there is an
             * additional part after the device name, it will be used as the
             * Modbus unit number.
             */
            SerialParameters parms = new SerialParameters();
            parms.setPortName(parts[1]);
            parms.setBaudRate(9600);
            parms.setDatabits(8);
            parms.setParity(AbstractSerialConnection.NO_PARITY);
            parms.setStopbits(1);
            parms.setFlowControlIn(AbstractSerialConnection.FLOW_CONTROL_DISABLED);
            parms.setEcho(false);
            try {
                ModbusRTUTransport transport = new ModbusRTUTransport();
                if (serialConnection == null) {
                    transport.setCommPort(SerialConnection.getCommPort(parms.getPortName()));
                } else {
                    transport.setCommPort(serialConnection);
                }
                transport.setEcho(false);
                return transport;
            } catch (IOException e) {
                return null;
            }
        }
        else if (parts[0].equalsIgnoreCase("tcp")) {
            /*
             * Create a ModbusTCPListener with the default interface value. The
             * second optional value is the TCP port number and the third
             * optional value is the Modbus unit number.
             */
            String hostName = parts[1];
            int port = Modbus.DEFAULT_PORT;

            if (parts.length > 2) {
                port = Integer.parseInt(parts[2]);
            }

            try {
                Socket socket = new Socket(hostName, port);
                logger.debug("connecting to {}", socket.toString());

                return new ModbusTCPTransport(socket);
            } catch (UnknownHostException x) {
                return null;
            } catch (IOException e) {
                return null;
            }
        }
        else if (parts[0].equalsIgnoreCase("udp")) {
            /*
             * Create a ModbusUDPListener with the default interface value. The
             * second optional value is the TCP port number and the third
             * optional value is the Modbus unit number.
             */
            String hostName = parts[1];
            int port = Modbus.DEFAULT_PORT;

            if (parts.length > 2) {
                port = Integer.parseInt(parts[2]);
            }
            AbstractUDPTerminal terminal = null;
            try {
                int currentPort = port;
                InetAddress currentAddress = InetAddress.getByName(hostName);
                // terminal = new UDPMasterTerminal(InetAddress.getByName(hostName));
                terminal = new AbstractUDPTerminal() {
                    @Override
                    public void activate() throws Exception {
                        if (!this.isActive()) {
                            if (this.socket == null) {
                                this.socket = new DatagramSocket();
                            }

                            logger.debug("UDPMasterTerminal::haveSocket():{}", this.socket.toString());
                            logger.debug("UDPMasterTerminal::raddr=:{}:rport:{}", currentAddress, currentPort);
                            this.socket.setReceiveBufferSize(1024);
                            this.socket.setSendBufferSize(1024);
                            this.socket.setSoTimeout(this.timeout);
                            this.transport = new ModbusUDPTransport(this);
                            this.active = true;
                        }

                        logger.debug("UDPMasterTerminal::activated");
                    }

                    @Override
                    public void deactivate() {
                        try {
                            logger.debug("UDPMasterTerminal::deactivate()");
                            if (this.socket != null) {
                                this.socket.close();
                            }

                            this.transport = null;
                            this.active = false;
                        } catch (Exception var2) {
                            logger.error("Error closing socket", var2);
                        }
                    }

                    @Override
                    public void sendMessage(byte[] msg) throws Exception {
                        DatagramPacket req = new DatagramPacket(msg, msg.length, currentAddress, currentPort);
                        this.socket.send(req);
                    }

                    @Override
                    public byte[] receiveMessage() throws Exception {
                        byte[] buffer = new byte[262];
                        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                        this.socket.setSoTimeout(this.timeout);
                        this.socket.receive(packet);
                        return buffer;
                    }
                };
                terminal.activate();
            } catch (UnknownHostException e) {
                e.printStackTrace();
                return null;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
            return terminal.getTransport();
        } else {
            throw new IllegalArgumentException("unknown type " + parts[0]);
        }
    }
}
